#ifndef RAY_TRACE_PRO_MESH_GEOMETRY
#define RAY_TRACE_PRO_MESH_GEOMETRY

#include "stdafx.h"
#include "KdTree.h"
#include "BBox.h"
#include "Ray.h"
#include "Material.h"
#include "RenderContext.h"

using namespace GxLibBasic;

namespace RayTracePro
{
	struct MeshFace
	{
		//// float4 v1:
		//float PlaneU, PlaneV, PlaneD; int Flags;
		//// float4 v2:
		//float K_beta_u, K_beta_v, K_beta_d, K_alpha_u;
		//// float4 v3:
		//float K_gamma_u, K_gamma_v, K_gamma_d, K_alpha_v;
		//// float4 v4:
		//int Normal1, Normal2, Normal3, Padding1;
		//// float4 v5:
		//int TexCoord1, TexCoord2, TexCoord3, Padding2;
	
		float PlaneX, PlaneY, PlaneZ, PlaneW;
		float AlphaX, AlphaY, AlphaZ, AlphaW;
		float BetaX, BetaY, BetaZ, BetaW;
		float GammaX, GammaY, GammaZ, GammaW;
		int Normal1, Normal2, Normal3, Padding;
		int TexCoord1, TexCoord2, TexCoord3, MaterialId;

		//// Helper functions
		//inline int GetProjectionAxis() const
		//{
		//	return Flags & 3;
		//}
		//inline int GetMaterialId() const
		//{
		//	return Flags>>2;
		//}
		//inline void SetProjectionAxis(int value)
		//{
		//	Flags = (Flags &(~3)) | value;
		//}
		//inline void SetMaterialId(int value)
		//{
		//	Flags = (Flags&3) + (value<<2);
		//}
	};

	class Material;

	class MeshGeometry
	{
	private:
		static cl::Kernel traceMeshKernel, traceMeshPacketKernel, fillIntersectionKernel;
		static bool kernelCreated;
		Array<BBox, true> faceBBoxes;
		KdTree<MeshFace> tree;
		bool dataInDevice;
	public:
		BBox Bounds;
		bool AutoFlipNormals;
		DeviceList<Vec3> Normals;
		DeviceList<Vec2> TexCoords;
		Array<MeshFace, true> Faces;
		Array<RefPtr<RayTracePro::Material>> Materials;
		inline void ReserveFaceBuffer(int size)
		{
			faceBBoxes.SetCapacity(size);
			Faces.SetCapacity(size);
		}
		void SetFaceCount(int count)
		{
			Faces.SetSize(count);
			faceBBoxes.SetSize(count);
		}
		void UpdateBounds();
		void SetFace(int i, const Vec3 verts[3], int norm[3], int tex[3], short materialId=-1);
		void UpdateAccelerateStructure();
		void ComputeBBox(BBox & box)
		{
			box = Bounds;
		}
		void ComputeBoundingSpheres(int depth, Array<Vec4, true> & buffer);
		KdTree<MeshFace> * GetKdTree() 
		{
			return &tree;
		}
		void LoadKdTree(const String & fileName)
		{
			FileStream fs(fileName, gxfmRead);
			int buf;
			fs.ReadBuffer(&tree.Bounds, 1);
			fs.ReadBuffer(&buf, 1);
			tree.Nodes.SetSize(buf);
			fs.ReadBuffer(&buf, 1);
			tree.Elements.SetSize(buf);
			fs.ReadBuffer(&buf, 1);
			tree.Leaves.SetSize(buf);
			fs.ReadBuffer(tree.Nodes.Buffer(), tree.Nodes.Count());
			fs.ReadBuffer(tree.Elements.Buffer(), tree.Elements.Count());
			fs.ReadBuffer(tree.Leaves.Buffer(), tree.Leaves.Count());
			tree.Elements.Clear();
			tree.Elements.Add(Faces);
		}
		void SaveKdTree(const String & fileName)
		{
			FileStream fs(fileName, gxfmWrite);
			fs.WriteBuffer(&tree.Bounds, 1);
			int buf = tree.Nodes.Count();
			fs.WriteBuffer(&buf, 1);
			buf = tree.Elements.Count();
			fs.WriteBuffer(&buf, 1);
			buf = tree.Leaves.Count();
			fs.WriteBuffer(&buf, 1);

			fs.WriteBuffer(tree.Nodes.Buffer(), tree.Nodes.Count());
			fs.WriteBuffer(tree.Elements.Buffer(), tree.Elements.Count());
			fs.WriteBuffer(tree.Leaves.Buffer(), tree.Leaves.Count());

		}
		bool ComputeIntersectionPred(RayTraceSystem * system, const Ray & ray);
		bool ComputeIntersection(RayTraceSystem * system, int rootNode, DifferentialGeometry & inter, 
			const Ray & ray, const RayDifferential & rayDiffIn, float & t);
		void ComputeIntersectionGPU(RayTraceSystem * system, 
									int start, 
									int end, 
									DeviceList<int> & rayIds, 
									DeviceList<TraverseResult> & continuation, // indicates the instance of each ray
									DeviceList<GeometryInstance> & instances, // transform matrices of each instance
									DeviceList<Ray> & rays, 
									DeviceList<RayDifferential> & rayDiffIns, 
									DeviceList<Vec4> & intermediateIntersections,
									DeviceList<DifferentialGeometry> & inters // result
									);
		void Compute_dNdx(DifferentialGeometry & inter, const Vec3 & dPdx, const Vec3 & dPdy);
		MeshGeometry();
	};

	typedef bool (*GeometryLoaderFunction)(RayTraceSystem * system, MeshGeometry & geometry, const char * fileName);

	class RayTraceSystem;

	class GeometryLoader
	{
	private:
		static RefPtr<Mapping<String, GeometryLoaderFunction>> loaders;
	public:
		static bool LoadGeometryFromFile(RayTraceSystem * system, MeshGeometry & geometry, const String & fileName);
		static void Init();
		static void Finalize()
		{
			loaders = 0;
		}
	};
	bool LoadSponge(MeshGeometry & geometry, int level);
}

#endif